# -*- coding: utf-8 -*-
from scrapy.spidermiddlewares.httperror import HttpError
from scrapy.utils.project import get_project_settings
from twisted.internet.error import TCPTimedOutError, ConnectionLost, DNSLookupError
from twisted.web._newclient import ResponseNeverReceived
from bs4 import BeautifulSoup

from bbs.items import ForumItem
from bbs.items import PostItem
from bbs.items import ReplyItem

from bbs.algorithm.operate import read_urls
from bbs.algorithm.operate import read_re_str
from bbs.algorithm.operate import delete_html_noise
from bbs.algorithm.operate import extract_title
from bbs.algorithm.operate import extract_time
from bbs.algorithm.operate import read_model
from bbs.algorithm.operate import extract_author
from bbs.algorithm.operate import extract_content

import scrapy


class ForumSpider(scrapy.Spider):
    """论坛爬虫程序实现爬取网络源码，对源码数据进行筛选提取字段数据"""
    name = 'forum'  # 名称
    allowed_domains = []  # 域名列表
    start_urls = []  # 网址列表

    # # 读取配置文件中的路径数据
    # 网址txt路径
    URL_TXT = get_project_settings().get("URL_TXT")
    # 数据json路径
    DATA_JSON = get_project_settings().get("DATA_JSON")

    def __init__(self, name=None, **kwargs):
        """构造函数"""
        super().__init__(name, **kwargs)
        # 读取网络论坛网址
        self.start_urls = read_urls(self.URL_TXT)
        # 遍历网址数组
        for url in self.start_urls:
            # print(url)
            # 根据网址获取域名
            domain = url[7:].split(sep="/")[0]
            # 将域名添加到域名数组
            self.allowed_domains.append(domain)
        pass

    def error_handler(self, failure):
        """爬虫爬取过程中的错误容错处理"""
        # 记录日志
        self.logger.error(msg=repr(failure))
        # failure.value.response
        # 按照不同的错误类型提示不同的错误信息
        if failure.check(HttpError):
            response = failure.value.response
            self.logger.info('网址爬取错误（HttpError） : %s', response.url)
        elif failure.check(DNSLookupError):
            # this is the original request
            request = failure.request
            self.logger.info('网址爬取错误（DNSLookupError） : %s', request.url)
        elif failure.check(TimeoutError):
            request = failure.request
            self.logger.info('网址爬取错误（TimeoutError） : %s', request.url)
        elif failure.check(ConnectionLost):
            request = failure.request
            self.logger.info('网址爬取错误（ConnectionLost） : %s', request.url)
        elif failure.check(TCPTimedOutError):
            request = failure.request
            self.logger.info('网址爬取错误（TCPTimedOutError） : %s', request.url)
        elif failure.check(ResponseNeverReceived):
            request = failure.request
            self.logger.info('网址爬取错误（ResponseNeverReceived） : %s', request.url)
        elif failure.check(ConnectionRefusedError):
            request = failure.request
            self.logger.info('网址爬取错误（ConnectionRefusedError） : %s', request.url)
        pass

    def start_requests(self):
        """开始发送请求爬取网页数据"""

        # 根据网址列表爬取网页
        for url in self.start_urls:
            # 返回调用函数，并进行容错处理
            yield scrapy.Request(url=url, callback=self.parse, dont_filter=True, errback=self.error_handler)
        pass

    def parse(self, response):
        """对网页源码数据进行提取"""

        # # 存储提取到的有价值的数据
        # 论坛数据
        # forums = []
        forum = ForumItem()
        # 主帖数据(字典)
        # post = PostItem()
        post = {}
        # 回帖数据(数组)
        # reply = ReplyItem()
        replys = []

        # # URL清洗:对论坛网址进行数据清洗
        # 判断网址是否跳转
        if response.url not in self.start_urls:
            # 访问论坛网址发生跳转,网址不在网址数组范围内
            # print("跳转"+"\t"+response.url)
            return
        # 判断帖子是否存在
        if len(response.body) < 26000:
            # 这里选定的网页长度标准是26000作为判断网址对应的论坛帖子数据是否存在(小于表明帖子不存在)
            # print("长度:"+str(len(response.body)) + "\t" + response.url)
            return

        # # HTML去噪:网页源码存在许多无用的信息
        # 读取文件中的正则表达式
        noise_txt = get_project_settings().get("NOISE_TXT")
        noise_re_str = read_re_str(noise_txt)
        # print(noise_re_str)
        # noise_re_str = "(<script[^>]*?>[\s\S]*?</script>|<style[^>]*?>[\s\S]*?</style>|<img[^>]*?>|<!--[\s\S]*?-->)"
        # 使用正则表达式匹配删除噪音信息
        html = delete_html_noise(noise_re_str, response)

        # # 提取论坛网页中的七个字段数据数据
        # 将论坛网址记录到论坛数据中
        forum["url"] = response.url
        # 提取帖子标题
        post["title"] = extract_title(html)
        # print(forum["url"]  + "\t" +  str(post["title"]))

        # # 提取主帖和回帖作者
        # 读取文件中的模板数据
        author_txt = get_project_settings().get("AUTHOR_TXT")
        # author_model = read_model(author_txt)
        # print(author_model)
        author_model = ["userNick", "center", "user_center_name", "dropmenu", "author", "authi", "user_name",
                        "user-name", "username", "readName", "name", "atl-info", "basic-info", "floor-userInfo",
                        "user-bbs-info", "user-info", "t-user", "tit_user", "pi", "tit", "postauthor", "post_user",
                        "f06c0", "uName", "atl-info", "ir-user"]
        # 获取主帖和回帖作者列表（列表不存在或列表长度大于0）
        post_reply_author_list = extract_author(html, author_model)

        # # 提取主帖和回帖时间
        # 读取文件中的正则表达式
        time_txt = get_project_settings().get("TIME_TXT")
        # time_re_str = read_re_str(time_txt)
        # print(time_re_str)
        time_re_str = "(>[\s]{0,}发表于[\s]{0,}([\d]{4}-[\d]{1,2}-[\d]{1,2}\s{1,}[\d]{1,2}:[\d]{1,2}:[\d]{1,2})|>[\s]{0,}发表于[：:]{0,}[\s]{0,}([\d]{4}-[\d]{1,2}-[\d]{1,2}\s{1,}[\d]{1,2}:[\d]{1,2})|>[\s]{0,}发表于[\s]{0,}([\d]{1,2}-[\d]{1,2}\s{1,}[\d]{1,2}:[\d]{1,2})|>[\s]{0,}发表于[：:]{0,}[\s]{0,}([\d]{4}-[\d]{1,2}-[\d]{1,2})|>[\s]{0,}发表于：[\s]{0,}\[([\d]{4}-[\d]{1,2}-[\d]{1,2}\s{1,}[\d]{1,2}:[\d]{1,2})\]|时间：[\s]{0,}([\d]{4}-[\d]{1,2}-[\d]{1,2}\s{1,}[\d]{1,2}:[\d]{1,2}:[\d]{1,2})|([\d]{4}-[\d]{1,2}-[\d]{1,2}\s{1,}[\d]{1,2}:[\d]{1,2}:[\d]{1,2})|([\d]{2}-[\d]{1,2}-[\d]{1,2}\s{1,}[\d]{1,2}:[\d]{1,2}:[\d]{1,2})|([\d]{2}-[\d]{1,2}-[\d]{1,2}\s{1,}[\d]{1,2}:[\d]{1,2})|([\d]{4}/[\d]{1,2}/[\d]{1,2}\s{1,}[\d]{1,2}:[\d]{1,2}:[\d]{1,2})|([\d]{4}-[\d]{1,2}-[\d]{1,2}\s{1,}[\d]{1,2}:[\d]{1,2})|>[\s]{0,}发表于[\s]{0,}(.*\s{1,}[\d]{1,2}:[\d]{1,2})|>[\s]{0,}发表于[\s]{0,}(.*\s{1,})[<>])"
        # 获取主帖和回帖时间列表（获取失败列表不存在或获取成功列表长度大于0）
        post_reply_time_list = extract_time(html, time_re_str)

        # # 提取主帖和回帖内容
        # 读取文件中的模板数据
        content_txt = get_project_settings().get("CONTENT_TXT")
        # content_model = read_model(content_txt)
        # print(content_model)
        content_model = ["t_fsz", "t_fsz1", "t_msgfont", "t_msgfont1", "post_width", "post_message", "post-content",
                         "post_content", "tpc_content", "topiccontent", "bbs-content", "txtmain", "con", "cont",
                         "content", "text", "article", "ilt_p", "viewmessage", "note-area"]
        # 获取主帖和回帖内容列表（列表不存在或列表长度大于0）
        post_reply_content_list = extract_content(html, content_model)

        # # 确定回帖数量
        # 获取主帖和回帖作者时间内容列表长度（不存在的长度为0）
        if post_reply_time_list is not None:
            post_reply_time_list_len = len(post_reply_time_list)
        else:
            post_reply_time_list_len = 0
        if post_reply_author_list is not None:
            post_reply_author_list_len = len(post_reply_author_list)
        else:
            post_reply_author_list_len = 0
        if post_reply_content_list is not None:
            post_reply_content_list_len = len(post_reply_content_list)
        else:
            post_reply_content_list_len = 0
        # 获取三个列表长度的最大值
        replys_len = max([post_reply_time_list_len, post_reply_author_list_len, post_reply_content_list_len])
        # 初始化回帖列表
        for i in range(1, replys_len):
            # 创建回帖字典并添加到回帖列表中
            reply = {}
            # reply = dict()
            replys.append(reply)
            pass
        # print(str(len(replys)))
        # print(replys_len)

        # # 主帖和回帖内容赋值
        if post_reply_content_list is not None:
            # 内容列表第一个直接复制为主帖内容
            post["content"] = post_reply_content_list[0].get_text().strip()
            # 当内容列表长度大于1才有回帖
            if len(post_reply_content_list) > 1:
                # 循环遍历回帖内容列表并进行回帖赋值
                for post_reply_content_index in range(1, len(post_reply_content_list)):
                    # print(post_reply_content_list[post_reply_content_index].get_text().strip())
                    replys[post_reply_content_index - 1]["content"] = post_reply_content_list[
                        post_reply_content_index].get_text().strip()
            pass

        # # 主帖和回帖作者赋值
        if post_reply_author_list is not None:
            # 使用BeautifulSoup搜索该标签下所有的超链接标签
            post_reply_author0_a_list = BeautifulSoup(markup=str(post_reply_author_list[0]),
                                                      features="lxml").find_all(name="a")
            if len(post_reply_author0_a_list) > 0:
                # print(post_reply_author_a_list[0].get_text().strip())
                # 处理超链接标签第一个是只看主帖作者位置（赋值第二个）
                if str(post_reply_author0_a_list[0].get_text().strip()).find("只看") != -1 and len(
                        post_reply_author0_a_list) > 1:
                    post["author"] = post_reply_author0_a_list[1].get_text().strip()
                # 处理超链接标签第一个是空白内容隐藏属性位置（赋值第二个）
                elif str(post_reply_author_list[0].get_text().strip()) == "" and len(post_reply_author0_a_list) > 1:
                    post["author"] = post_reply_author0_a_list[1].get_text().strip()
                else:
                    # 直接将第一个超链接内容赋值
                    post["author"] = post_reply_author0_a_list[0].get_text().strip()
                pass
            else:
                # 如果没有超链接标签直接将该标签的内容赋值给主帖作者
                post["author"] = post_reply_author_list[0].get_text().strip()
            # 只有作者列表长度大于1才能说明有回帖
            if len(post_reply_author_list) > 1:
                # 循环遍历回帖作者列表并进行回帖赋值
                for post_reply_author_index in range(1, len(post_reply_author_list)):
                    # 使用BeautifulSoup搜索该标签下所有的超链接标签
                    post_reply_author_a_list = BeautifulSoup(
                        markup=str(post_reply_author_list[post_reply_author_index]), features="lxml").find_all(name="a")
                    if len(post_reply_author_a_list) > 0:
                        # print(post_reply_author_a_list[0].get_text().strip())
                        # 处理超链接标签第一个是只看主帖作者位置（赋值第二个）
                        if str(post_reply_author_a_list[0].get_text().strip()).find("只看") != -1 and len(
                                post_reply_author_list) > post_reply_author_index + 1:
                            replys[int((post_reply_author_index - 1) / 2)]["author"] = post_reply_author_list[
                                post_reply_author_index + 1].get_text().strip()
                            # 跳过下一次循环
                            post_reply_author_index += 1
                        # 处理超链接标签第一个是空白内容隐藏属性位置（赋值第二个）
                        elif str(post_reply_author_a_list[0].get_text().strip()) == "" and len(
                                post_reply_author_list) > post_reply_author_index + 1:
                            replys[int((post_reply_author_index - 1) / 2)]["author"] = post_reply_author_list[
                                post_reply_author_index + 1].get_text().strip()
                            # 跳过下一次循环
                            post_reply_author_index += 1
                        else:
                            # 最后一个超链接标签
                            if str(post_reply_author_a_list[0].get_text().strip()) != "" or str(post_reply_author_a_list[0].get_text().strip()).find("只看") == -1:
                                # 直接将第一个超链接内容赋值
                                replys[post_reply_author_index - 1]["author"] = post_reply_author_a_list[
                                    0].get_text().strip()

                        pass
                    else:
                        # 直接将该标签的内容赋值
                        # print(post_reply_author_list[post_reply_author_index].get_text().strip())
                        replys[post_reply_author_index - 1]["author"] = post_reply_author_list[
                            post_reply_author_index].get_text().strip()
                pass

        # # 主帖和回帖时间赋值
        if post_reply_time_list is not None:
            # 获取匹配的时间格式数据进行主帖时间赋值
            post_reply_time_tuple0 = post_reply_time_list[0]
            for post_reply_time_tuple0_index in range(1, len(post_reply_time_tuple0)):
                # print(post_reply_time_tuple0[post_reply_time_tuple0_index])
                if post_reply_time_tuple0[post_reply_time_tuple0_index] != "":
                    post["time"] = post_reply_time_tuple0[post_reply_time_tuple0_index]
                    break
                pass
            # 只有时间列表长度大于1才能说明有回帖
            if len(post_reply_time_list) > 1:
                # 循环遍历回帖时间列表进行赋值
                for post_reply_time_list_index in range(1, len(post_reply_time_list)):
                    # 所有的时间格式匹配规则返回元组数据类型
                    # print(post_reply_time_list[post_reply_time_list_index])
                    # print(type(post_reply_time_list[post_reply_time_list_index]))
                    post_reply_time_tuple = post_reply_time_list[post_reply_time_list_index]
                    # 获取匹配的时间格式数据进行主帖时间赋值
                    for post_reply_time_tuple_index in range(1, len(post_reply_time_tuple)):
                        # 元组中必然有一个符合规则的时间格式
                        if post_reply_time_tuple[post_reply_time_tuple_index] != "":
                            # print(post_reply_time_tuple[post_reply_time_tuple_index])
                            replys[post_reply_time_list_index - 1]["time"] = post_reply_time_tuple[
                                post_reply_time_tuple_index]
                            break
            pass

        # 将主帖字典赋值给论坛数据
        forum["post"] = post
        # 将回帖列表添加到论坛数据
        forum["replys"] = replys
        # print(forum)
        # yield同return 返回Item数据类型给Pipeline管道程序处理
        yield forum
        pass
